/*
 * Comilla – replacer plugin for JETERS
 * Copyright (C) 2006–2008  Tobias Knerr
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 */

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.jeters.componentInterface.UIComponent;
import net.sf.jeters.componentInterface.dataStructs.UIRequest_Output;
import net.sf.jeters.componentInterface.editables.EditableText;
import net.sf.jeters.componentInterface.editables.MediaWikiText;
import net.sf.jeters.components.RegExReplacer;
import net.sf.jeters.util.StringProtector;

/**
 * replacer class for JETERS that checks 
 * German typography in MediaWiki articles.
 * Uses German comments due to expected audience.  
 *
 * @author Tobias Knerr
 */ 
public class ComillaReplacer extends RegExReplacer {

	/* Konfigurationsvariablen */
	
	private String conf_EditDescription = "Typografische Korrekturen: ";	
	
	private Boolean conf_Whitespace = false;

 	private String conf_stringBeforePercent = " "; //"" means: no changes
	
	private Boolean conf_nbspForParenthesis = false;
 	private Boolean conf_nbspForUnit = true;
 	private Boolean conf_nbspForPage = true;
 	private Boolean conf_nbspForAbbreviations = false;
 	
 	private Boolean conf_startOfSentenceAbbrCheck = false;
 	
	private Boolean conf_addSpaceAfterPunctuationChars = false;
	
	/** legt fest, ob beim Aufteten von Pinyin-Wörtern mit Apostroph gewarnt
	 *  werden soll, da dort evtl. keine Apostrophkorrektur erwünscht ist */
	private Boolean conf_pinyinApostropheWarning = true;
	
	private Integer conf_splitEnumerationAfter = 1;
	
	/** kontrolliert Funktionalität, die Auszeichnungen (Kursivschreibung) von
	 *  Text auf nahestehende Klammern, Anführungs- und Satzzeichen überträgt */
	private Boolean conf_expandMarkup = false;
	
	
	/**
	 * regulärer Ausdruck, der Pinyin-Wörter erkennt
	 * (erstellt vom Nutzer chrislb der deutschsprachigen Wikipedia)
	 */
	private static final String PINYIN_REGEX
	        =   "^(?!\')"
	          + "(((([bcdfghjklmnpqrstwxyz]|[zcs]h)(i([āáǎàa][ōóǒòo]|[ūúǔùuāáǎàaēéěèe])?|[ūúǔùu]"
	          + "([āáǎàa][īíǐìi]|[īíǐìiāáǎàaēéěèeōóǒòo])?|[ǖǘǚǜü][ēéěèe]?))"
	          + "|er(?![āáǎàaēéěèeīíǐìiōóǒòoūúǔùu])"
	          + "|((([bcdfghklmnprstwyz]|[zcs]h)|')?([āáǎàa][ōóǒòoīíǐìi]?|[ēéěèe][īíǐìi]?|[ōóǒòo][ūúǔùu]?)))"
	          + "((ng|n)(?![āáǎàaēéěèeōóǒòo]))?"
	          + "|')*$";
	        		
	//StringProtector, der für Schutzersetzungen zum Einsatz kommt
	private final StringProtector stringProtector = new StringProtector("≏≏≏","≑≑≑");
	
	//kritische Zeichenketten, auf die nach der Ersetzung geprüft wird (deuten stark auf Fehler hin)
	private String[] problemStrings = { "≑", "≏", "※", "⇦", "⇨", "⊾" };
	
	
	
	/**
	 * creates the regex-pairs for the replacements attribute.
	 * erzeugt die Paare von regulären Ausdrücken für das Attribut replacements.
	 */	
	private void createReplacements(boolean schweizbezogen){

		replacements = new ArrayList<ReplacementElement>();
		
		
		// TEMPORÄRE ERSETZUNGEN			
		
		//Aufruf der Schutzfunktionalität durch ein Replacement mit eigener Methode
		replacements.add( new CustomReplacement(){				
			public String apply(String s){
				return protect(s);
			}
		} );

		//verschiedene Schutzersetzungen
		                                            
		//HTML-Schutzersetzungen
		
		//HTML-codierte geschützte Leerzeichen maskieren (mit Unicode-Zeichen 8251 (Hex. 0x203B) – kommt hoffentlich in keinem deutschen Artikel vor…)
		replacements.add(new RegExReplacement(-1,"&nbsp;","※"));

		//HTML-Sonderzeichen (sonst wird das Semikolon als Satzzeichen missverstanden)
		replacements.add(new RegExReplacement(-1,"&([^ ;\\}]+);","HTML{$1}LMTH"));
				
		
		//Abkürzungen                                                                 
		
		ReplacementGroup repAbkürzungKorrekt = new ReplacementGroup(1); 
		ReplacementGroup repAbkürzungGeschLZ = new ReplacementGroup(1,"gesch. LZ bei Abk."); 
		ReplacementGroup repAbkürzungKorrigiert = new ReplacementGroup(1,"Abk.");
		
		//B.C.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])B\\.C\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{beforechrist}KBA$2"));
		//c. t.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])c\\.※t\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{cumtempore_nbsp}KBA$2")); 
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])c\\.[ ]?t\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{cumtempore}KBA$2","„c. t.“",1)); 
		//d. h.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])d\\.※h\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{dasheißt_nbsp}KBA$2")); 
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])d\\. h\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{dasheißt}KBA$2","„d. h.“",1)); 
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])d[\\.]?[ ※]?h\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{dasheißt}KBA$2","„d. h.“",4)); 
		//etc.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])etc\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{et_cetera}KBA$2")); 
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])e[\\.]?[ ※]?t[\\.]?[ ※]?c[\\.]?([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{et_cetera}KBA$2","„etc.“",4)); 
		//e. V.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])e\\.※V\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{eingetragenerverein_nbsp}KBA$2")); 
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])e\\. V\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{eingetragenerverein}KBA$2","„e. V.“",1)); 
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])e\\.[ ※]?V\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{eingetragenerverein}KBA$2","„e. V.“",4)); 
		//F.A.Z.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])F\\.A\\.Z\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{FrankfurterAllgemeineZeitung}KBA$2"));
		//i. d. R.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])i\\.※d\\.※R\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{inderregel_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])i\\.[※ ]d\\.[※ ]R\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{inderregel}KBA$2","„u. v. a.“",1));
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])i[\\.]?[ ※]?d[\\.]?[ ※]?R\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{inderregel}KBA$2","„u. v. a.“",4));
		//.NET (ist zwar keine echte Abkürzung, verursacht aber die gleichen Probleme)
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])\\.NET([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{dotnet}KBA$2"));
		//o. Ä.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])o\\.※Ä\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{oderähnliches_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])o\\. Ä\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{oderähnliches}KBA$2","„u. Ä.“",1));
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])o[\\.]?[ ※]?[äÄ]\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{oderähnliches}KBA$2","„u. Ä.“",4));
		//s. o.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])s\\.※o\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{sieheoben_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])s\\. o\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{sieheoben}KBA$2","„s. o.“",1));
		//s. t.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])s\\.※t\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{sinetempore_nbsp}KBA$2")); 
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])s\\.[ ]?t\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{sinetempore}KBA$2","„s. t.“",1)); 
		//s. u.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])s\\.※u\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{sieheunten_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])s\\. u\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{sieheunten}KBA$2","„s. u.“",1));
		//u. a.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u\\.※a\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undandere_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u\\. a\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undandere}KBA$2","„u. a.“",1));
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u[\\.]?[ ※]?a\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undandere}KBA$2","„u. a.“",4));
		//u. v. a.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u\\.※v\\.※a\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undvieleandere_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u\\.[※ ]v\\.[※ ]a\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undvieleandere}KBA$2","„u. v. a.“",1));
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u[\\.]?[ ※]?v[\\.]?[ ※]a\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undvieleandere}KBA$2","„u. v. a.“",4));
		//u. Ä.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u\\.※Ä\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undähnliches_nbsp}KBA$2"));
		repAbkürzungGeschLZ.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u\\. Ä\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undähnliches}KBA$2","„u. Ä.“",1));
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])u[\\.]?[ ※]?[äÄ]\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{undähnliches}KBA$2","„u. Ä.“",4));
		//U.S.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])U\\.S\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{unitedstates}KBA$2"));
		//v. a.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])v\\.※a\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{vorallem_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])v[\\.]? ?a\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{vorallem}KBA$2","„v. a.“",1));
		//z. B.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])z\\.※B\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{zumbeispiel_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])z\\. B\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{zumbeispiel}KBA$2","„z. B.“",1));
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])z[\\.]?[ ※]?B[\\.]?([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{zumbeispiel}KBA$2","„z. B.“",4)); 
		//z. T.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])z\\.※T\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{zumteil_nbsp}KBA$2"));
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])z\\. T\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{zumteil}KBA$2","„z. T.“",1));
		repAbkürzungKorrigiert.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])z[\\.]?[ ※]?T[\\.]?([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{zumteil}KBA$2","„z. T.“",4)); 
		//S.
		repAbkürzungKorrekt.add(new RegExReplacement(1,"(^|[^a-zA-ZaöüÄÖÜß0-9])S\\.([^a-zA-ZaöüÄÖÜß0-9]|$)","$1ABK{seite}KBA$2")); 
			
		replacements.add(repAbkürzungKorrekt);
		replacements.add(repAbkürzungKorrigiert); 
				
		
		// ERSETZUNGEN
		
		//Abkürzungs-Platzhalter für Varianten ohne geschütztes Leerzeichen
		//bei entsprechender Konfiguration durch die mit gesch. LZ ersetzen
		if (conf_nbspForAbbreviations) {	
			
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{cumtempore\\}KBA","ABK{cumtempore_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{dasheißt\\}KBA","ABK{dasheißt_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{eingetragenerverein\\}KBA","ABK{eingetragenerverein_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{oderähnliches\\}KBA","ABK{oderähnliches_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{sieheoben\\}KBA","ABK{sieheoben_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{sinetempore\\}KBA","ABK{sinetempore_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{sieheunten\\}KBA","ABK{sieheunten_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{undandere\\}KBA","ABK{undandere_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{undvieleandere\\}KBA","ABK{undvieleandere_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{undähnliches\\}KBA","ABK{undähnliches_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{vorallem\\}KBA","ABK{vorallem_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{zumbeispiel\\}KBA","ABK{zumbeispiel_nbsp}KBA")); 
			repAbkürzungGeschLZ.add(new RegExReplacement(1,"ABK\\{zumteil\\}KBA","ABK{zumteil_nbsp}KBA")); 
			
			replacements.add(repAbkürzungGeschLZ); 
			
		}
				
		//Auszuschreibende Abkürzungen
		//(gewöhnlich bei Großschreibung am Satzanfang)
		if (conf_startOfSentenceAbbrCheck) {
			
			ReplacementGroup repAbkAusschreiben = new ReplacementGroup(1, "Abk. ausgeschrieben");
			
			repAbkAusschreiben.add(new RegExReplacement(-1, "Z[.]?[ ※]?B.", "Zum Beispiel", 5)); 
			repAbkAusschreiben.add(new RegExReplacement(-1, "M[.]?[ ※]?a[.]?[ ※]?W.", "Mit anderen Worten", 5)); 
			repAbkAusschreiben.add(new RegExReplacement(-1, "M[.]?[ ※]?M[.]?[ ※]?n.", "Meiner Meinung nach", 5));

			replacements.add(repAbkAusschreiben);
			
		}
		
		
		
		//Vorgriff auf Anführungszeichen (da sonst als Mehrfachkomma erkannt):
		//gelegentliche „Imitation“ des unteren Anführungszeichens mit ,, 
		//Erkennungsschema: <,,> (<irgendwelche Zeichen außer Anführungszeichen>)=(1) <">
		//Ersetzungsschema: <„> (1) <“>
		replacements.add(new RegExReplacement(-1,",,([^„“\"]*)\"","„$1“","Anführungszeichen",4));
			
	
		//Mehrfache Satzzeichen
		
		//Auslassungspunkte
		//müssen möglichst früh behandelt werden, damit die Punkte nicht mehr als Satzende gelten
		//Erkennungsschema: <3 Punkte>
		//Ersetzungsschema: <Auslassungspunkte>
		replacements.add(new RegExReplacement(-1,"\\.\\.\\.", "…", "Auslassungspunkte",2));
		 
		//sonstige mehrfache Satzzeichen (gewöhnlich Tippfehler oder aber unenzyklopädische Betonungen, etwa durch !!!)
		//Erkennungsschema: <zwei oder mehr Satzzeichen eines Typs>
		//Ersetzungsschema: <ein Satzzeichen dieses Typs>
		replacements.add(new RegExReplacement(-1,"([\\.,;:!\\?])(\\1)+", "$1", "mehrfache Satzzeichen",4));  


		//Grad- und Zollzeichen
		
		//Koordinaten-Blöcke
		//Grad, Gradminuten und Gradsekunden
		replacements.add(new RegExReplacement(-1,"([0-9]+(?:[½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]|[,\\.][0-9]+)?)[ ※]*°[ ※]*([0-9]+(?:[½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]|[,\\.][0-9]+)?)[ ※]*['′][ ※]*([0-9]+(?:[½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]|[,\\.][0-9]+)?)[ ※]*(?:[\"“″]|HTML\\{quot\\}LMTH)","$1°※$2′※$3″","Koordinaten",5));            
		//Grad und Gradminuten
		replacements.add(new RegExReplacement(-1,"([0-9]+(?:[½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]|[,\\.][0-9]+)?)[ ※]*°[ ※]*([0-9]+(?:[½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]|[,\\.][0-9]+)?)[ ※]*['′]","$1°※$2′","Koordinaten",5));
									
		//falsches Leerzeichen vor Gradzeichen
		//danach muss <LZ, gesch. LZ, Bindestrich, Bis-Strich, ), “ oder Satzzeichen> kommen, um Probleme insbesondere mit „°C“ zu verhindern
		//Erkennungsschema: <Ziffer oder Unicode-Bruch>=(1) <LZ> <°> <Zeilenende oder (gesch.) LZ, ')', “, Bindestrich, Bis-Strich oder Satzzeichen>=(2)
		//Ersetzungsschema: (1) <°> (2)
		replacements.add(new RegExReplacement(-1,"([0-9½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]) °([ ※)“\\-–\\.,;:!?]|$)","$1°$2","Grad",3));            
														
		//falsches Zeichen für Gradminuten oder falsches Leerzeichen davor
		//danach muss <LZ, gesch. LZ, Bindestrich, Bis-Strich, ), “ oder Satzzeichen> kommen, um Probleme z.B. mit „'ne“ zu verhindern
		//Erkennungsschema: <Ziffer oder Unicode-Bruch>=(1) <LZ oder nichts> <' oder ′> <Zeilenende oder (gesch.) LZ, ')', “, Bindestrich, Bis-Strich oder Satzzeichen>=(2)
		//Ersetzungsschema: (1) <′> (2)
		replacements.add(new RegExReplacement(-1,"([0-9½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]) ?[\\'′]([ ※)“\\-–\\.,;:!?]|$)","$1′$2","Gradminuten",3));
		 
		//falsches Zeichen für Zoll bzw. Gradsekunden oder falsches Leerzeichen davor
		//sollten vor den Anführungszeichen behandelt werden, damit sie nicht als solche verarbeitet werden
		//um Fehler mit Zahl vor einem schließenden Anführungszeichen zu reduzieren, darf in diesem Satz bzw. in dieser Klammer oder in diesem Absatz vorher kein " stehen, damit die Ersetzung stattfindet
		//Erkennungsschema: (<Zeilenbeginn oder schließendes Satzzeichen bzw. öffnende Klammer> <beliebig viele Zeichen (mind. eines), aber kein Zeilenumbruch, kein " und keine schließenden Satzzeichen (Punkt, Ausrufezeichen, Fragezeichen, schließende runde Klammer)>)=(1) <Ziffer oder Unicode-Bruch>=(2) <LZ oder nichts> <" oder ″> (<Zeilenende oder (gesch.) LZ, ')', “, Bindestrich, Bis-Strich oder Satzzeichen>)=(4)
		//Ersetzungsschema: (1) (2) <″> (4)
		replacements.add(new RegExReplacement(-1,"(^|[\n\\.!\\?\\(][^\"\n\\.!?)]*)([0-9½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]) ?(\"|″)([ ※)“\\-–\\.,;:!\\?]|$)","$1$2″$4","Zoll/Gradsekunden",3));    
				
		
		//korrekte Anführungszeichen 
		
		//eingefasste falsche Anführungszeichen

		//falsches schließendes Anführungszeichen
		//Erkennungsschema: <„> (<irgendwelche Zeichen außer Anführungszeichen>)=(1) <">
		//Ersetzungsschema: <„> (1) <“>
		replacements.add(new RegExReplacement(-1,"„([^“\"]*)\"","„$1“","Anführungszeichen",4));
		 
		//zwei falsche Anführungszeichen
		//Erkennungsschema: <"> <irgendwelche Zeichen außer Anführungszeichen>=(1) <">
		//Ersetzungsschema: <„> (1) <“>
		replacements.add(new RegExReplacement(-1,"\"([^\"„“]*)\"","„$1“","Anführungszeichen",4));
		
		//»…« ersetzen	
		//Erkennungsschema: <bel. Zeichen außer » oder « ab Zeilenanfang>=(1) <»> <irgendwelche Zeichen außer Anführungszeichen>=(1) <«>
		//Ersetzungsschema: (1) <„> (2) <“>
		replacements.add(new RegExReplacement(-1,"^([^»«]*)»([^»«]*)«","$1„$2“","Anführungszeichen",4));
				
		if( !schweizbezogen ){
			
			//«…» ersetzen	(nur, wenn nicht schweizbezogen)	
			//Erkennungsschema: <bel. Zeichen außer » oder « ab Zeilenanfang>=(1) <»> <irgendwelche Zeichen außer Anführungszeichen>=(1) <«>
			//Ersetzungsschema: (1) <„> (2) <“>
			replacements.add(new RegExReplacement(-1,"^([^«»]*)«([^«»]*)»","$1„$2“","Anführungszeichen",4));

		}
		
		
		//einfache Anführungszeichen
		
		//mit ‘…’ (englische Variante)
		//Erkennungsschema: <‘> <irgendwelche Zeichen außer einfachen Anführungszeichen>=(1) <’>
		//Ersetzungsschema: <‚> (1) <‘>
		replacements.add(new RegExReplacement(-1,"‘([^‚‘’]*)’","‚$1‘","Anführungszeichen",4));
		
		//mit '…'
		//Erkennungsschema: <LZ oder Zeilenanfang>=(1) <'> <irgendwelche Zeichen außer einfachen Anführungszeichen und Hochkommata, kein LZ am Anfang oder Ende>=(2) <'> <LZ, Satzzeichen oder Zeilenende>=(3)
		//Ersetzungsschema: (1) <‚> (2) <‘> (3)
		replacements.add(new RegExReplacement(-1,"(^| )'([^ ‚‘’'][^‚‘’']*[^ ‚‘’'])'([ ,;.:!?]|$)","$1‚$2‘$3","Anführungszeichen",4));
				
		
		//Striche
			
		//Bis-Strich bei verlinkten Jahreszahlen
		/*++ an neuen Schutzmechanismus anpassen ++*/
		//Erkennungsschema: (<[[> <1–4 Ziffern> <]]>)=(1) <LZ, gesch. LZ oder nichts> <Bindestrich> <LZ, gesch. LZ oder nichts> (<[[> <1–4 Ziffern> <]]>)=(4)
		//Ersetzungsschema: (1) <Bis-Strich> (4)
		/*++ FUNKTIONIERT NICHT MEHR replacements.add(new RegExReplacement(-1,"\\[\\[([0-9]{1,4})\\]\\](?: |※)?-(?: |※)?\\[\\[([0-9]{1,4})\\]\\]","[[$1]]–[[$2]]","Bis-Strich",4)); ++*/           
		
		//Bis-Strich bei Zahlen allgemein (und Zahlen mit Gradangabe oder Punkt)
		//Erkennungsschema: (<Textanfang oder Zeichen, das keine Ziffer und kein Strich ist> <x Ziffern, x aus 1–9> <Grad, -minuten, -sekunden oder nichts>)=(1) <LZ, gesch. LZ oder nichts> <Bindestrich> <LZ, gesch. LZ oder nichts> (<x oder mehr Ziffern> <Grad, -minuten, -sekunden oder nichts> <Zeichen, das keine Ziffer und kein Strich ist oder Textende>)=(2)
		//Ersetzungsschema: (1) (2) <Bis-Strich> (5) (6)
		replacements.add(new RegExReplacement(-1,"((?:^|[^0-9–\\-])[0-9]+[°′″\\.]?)[ ※]*-[ ※]*([0-9]+[°′″\\.]?(?:[^0-9–\\-]|$))","$1–$2","Bis-Strich",4));

		//Streckenstrich
		/*++ TODO: bei mehreren Wörtern ohne Zahlen am Stück++*/
	
		//Gedankenstriche

		ReplacementGroup repGedankenstriche = new ReplacementGroup(1,"Gedankenstriche");

		//einschließende Gedankenstriche 
	
		//*++ vielleicht mit Java-Code lösen? würde etwa unbeschränkte Anzahl Klammernpaare erlauben ++*
		//*++ Behandlung von runden Klammern fehlt noch. -> Evtl. vorher maskieren, hinterher wieder aufheben? ++*

		/*++ alte Version, die auch mit geschützten LZ erkennt – wurde deaktiviert, damit die gesch. LZ nicht entfernt werden 
		
		//ohne Komma/Semikolon nach dem schließenden Gedankenstrich
		//
		//Erkennungsschema:
		//(1): Zeilenanfang, newline oder schließendes Satzzeichen
		//(2): Mindestens einmal eines der folgenden: [ Zeichen, das KEIN newline, Viertelg.-/Halbg.-/Geviertstrich oder schließendes Satzz. (.!?) ist ] ODER [ ein Viertel-/Halbgeviertstrich, vor dem kein (evtl. gesch.) LZ oder Viertelgev.str. (Grund: z. B. Binde- und Bis-Striche) ] ODER [ gleiches wie 2. Alternative, nur ohne LZ/Strich _danach_ (Grund z. B. Minus) ]; DANACH ein Zeichen, das kein (evtl. gesch.) LZ ist 
		//-A-: ENTWEDER ein oder zwei Viertel-, ein Halb-, oder ein Geviertstrich, umgeben von evtl. geschützten LZ (vorher und nachher je min. 1) ODER ein Geviertstrich mit beliebig vielen LZ vorher und nachher (auch 0!) 
		//(3): wie (2), nur mit Nicht-LZ davor und danach
		//-B-: ENTWEDER ein oder zwei Viertel-, ein Halb-, oder ein Geviertstrich, umgeben von evtl. geschützten LZ (vorher und nachher je min. 1, nachher auch Komma möglich) ODER ein Geviertstrich mit beliebig vielen LZ vorher und nachher (auch 0!, nachher auch Komma möglich)
		//(4): wie (2), nur mit Nicht-LZ davor statt danach
		//(5): Zeilenende, newline oder schließendes Satzzeichen
		//Ersetzungsschema:
		//(1) (2) <LZ> <Gedankenstrich> <gesch. LZ> (3) <gesch. LZ> <Gedankenstrich> <LZ> (4) (5)
		repGedankenstriche.add(new RegExReplacement(-1,
		 "(^|[\n\\.!\\?])"
		+"((?:[^\n\\-–—\\.!\\?]|[^ ※\\-][\\-–]|[\\-–][^ ※\\-])+(?:[^※ ]))"
		+"(?:[※ ]+(?:[\\-–]|\\-\\-|—)[※ ]+|[※ ]*—[※ ]*)"
		+"((?:[^※ ])(?:[^\n\\-–—\\.!\\?]|[^ ※\\-][\\-–]|[\\-–][^ ※\\-])+(?:[^※ ]))"
		+"(?:[※ ]+(?:[\\-–]|\\-\\-|—)[※ ]+|[※ ]*—[※ ]*)"
		+"((?:[^※ ])(?:[^\n\\-–—\\.!\\?]|[^ ※\\-][\\-–]|[\\-–][^ ※\\-])+)"
		+"(([\n\\.!\\?])|$)"
		,(conf_nbspForParenthesis)?("$1$2 –※$3※– $4$5"):("$1$2 – $3 – $4$5")
		,"einschließend",5));

		//mit Komma/Semikolon nach dem schließenden Gedankenstrich
		//
		//Erkennungsschema (nur Unterschied zur Version ohne Komma, statt dem Punkt -B- dort, Nummern danach werden um 1 erhöht)
		//-B1-: wie B oben, nur ohne verpflichtendes LZ danach
		//(4) : Komma oder Semikolon
		//-B2-: Bel. viele LZ
		//Ersetzungsschema
		//(1) (2) <LZ> <Gedankenstrich> <gesch. LZ> (3) <gesch. LZ> <Gedankenstrich> (4) <LZ> (5) (6)
		repGedankenstriche.add(new RegExReplacement(-1,
		 "(^|[\n\\.!\\?])"
		+"((?:[^\n\\-–—\\.!\\?]|[^ ※\\-][\\-–]|[\\-–][^ ※\\-])+(?:[^※ ]))"
		+"(?:[※ ]+(?:[\\-–]|\\-\\-|—)[※ ]+|[※ ]*—[※ ]*)"
		+"((?:[^※ ])(?:[^\n\\-–—\\.!\\?]|[^ ※\\-][\\-–]|[\\-–][^ ※\\-])+(?:[^※ ]))"
		//Beginn Unterschied der Kommaversion//
		+"(?:[※ ]+(?:[\\-–]|\\-\\-|—)[※ ]*|[※ ]*—[※ ]*)"
		+"([,;])"
		+"[ ]*"
		//Ende Unterschied der Kommaversion//
		+"((?:[^※ ])(?:[^\n\\-–—\\.!\\?]|[^ ※\\-][\\-–]|[\\-–][^ ※\\-])+)"
		+"(([\n\\.!\\?])|$)"
		,(conf_nbspForParenthesis)?("$1$2 –※$3※–$4 $5$6"):("$1$2 – $3 –$4 $5$6")
		,"einschließend",5));
		
		++*/
		
		
		//ohne Komma/Semikolon nach dem schließenden Gedankenstrich
		//
		//Erkennungsschema:
		//(1): Zeilenanfang, newline oder schließendes Satzzeichen
		//(2): Mindestens einmal eines der folgenden: [ Zeichen, das KEIN newline, Viertelg.-/Halbg.-/Geviertstrich oder schließendes Satzz. (.!?) ist ] ODER [ ein Viertel-/Halbgeviertstrich, vor dem kein (evtl. gesch.) LZ oder Viertelgev.str. (Grund: z. B. Binde- und Bis-Striche) ] ODER [ gleiches wie 2. Alternative, nur ohne LZ/Strich _danach_ (Grund z. B. Minus) ]; DANACH ein Zeichen, das kein (evtl. gesch.) LZ ist 
		//-A-: ENTWEDER ein oder zwei Viertel-, ein Halb-, oder ein Geviertstrich, umgeben von evtl. geschützten LZ (vorher und nachher je min. 1) ODER ein Geviertstrich mit beliebig vielen LZ vorher und nachher (auch 0!) 
		//(3): wie (2), nur mit Nicht-LZ davor und danach
		//-B-: ENTWEDER ein oder zwei Viertel-, ein Halb-, oder ein Geviertstrich, umgeben von evtl. geschützten LZ (vorher und nachher je min. 1, nachher auch Komma möglich) ODER ein Geviertstrich mit beliebig vielen LZ vorher und nachher (auch 0!, nachher auch Komma möglich)
		//(4): wie (2), nur mit Nicht-LZ davor statt danach
		//(5): Zeilenende, newline oder schließendes Satzzeichen
		//Ersetzungsschema:
		//(1) (2) <LZ> <Gedankenstrich> <gesch. LZ> (3) <gesch. LZ> <Gedankenstrich> <LZ> (4) (5)
		repGedankenstriche.add(new RegExReplacement(-1,
		 "(^|[\n\\.!\\?])"
		+"((?:[^\n\\-–—\\.!\\?]|[^ \\-][\\-–]|[\\-–][^ \\-])+(?:[^ ]))"
		+"(?:[ ]+(?:[\\-–]|\\-\\-|—)[ ]+|[ ]*—[ ]*)"
		+"((?:[^ ])(?:[^\n\\-–—\\.!\\?]|[^ \\-][\\-–]|[\\-–][^ \\-])+(?:[^ ]))"
		+"(?:[ ]+(?:[\\-–]|\\-\\-|—)[ ]+|[ ]*—[ ]*)"
		+"((?:[^ ])(?:[^\n\\-–—\\.!\\?]|[^ \\-][\\-–]|[\\-–][^ \\-])+)"
		+"(([\n\\.!\\?])|$)"
		,(conf_nbspForParenthesis)?("$1$2 –※$3※– $4$5"):("$1$2 – $3 – $4$5")
		,"einschließend",5));

		//mit Komma/Semikolon nach dem schließenden Gedankenstrich
		//
		//Erkennungsschema (nur Unterschied zur Version ohne Komma, statt dem Punkt -B- dort, Nummern danach werden um 1 erhöht)
		//-B1-: wie B oben, nur ohne verpflichtendes LZ danach
		//(4) : Komma oder Semikolon
		//-B2-: Bel. viele LZ
		//Ersetzungsschema
		//(1) (2) <LZ> <Gedankenstrich> <gesch. LZ> (3) <gesch. LZ> <Gedankenstrich> (4) <LZ> (5) (6)
		repGedankenstriche.add(new RegExReplacement(-1,
		 "(^|[\n\\.!\\?])"
		+"((?:[^\n\\-–—\\.!\\?]|[^ \\-][\\-–]|[\\-–][^ \\-])+(?:[^ ]))"
		+"(?:[ ]+(?:[\\-–]|\\-\\-|—)[ ]+|[ ]*—[ ]*)"
		+"((?:[^ ])(?:[^\n\\-–—\\.!\\?]|[^ \\-][\\-–]|[\\-–][^ \\-])+(?:[^ ]))"
		//Beginn Unterschied der Kommaversion//
		+"(?:[ ]+(?:[\\-–]|\\-\\-|—)[ ]*|[ ]*—[ ]*)"
		+"([,;])"
		+"[ ]*"
		//Ende Unterschied der Kommaversion//
		+"((?:[^ ])(?:[^\n\\-–—\\.!\\?]|[^ \\-][\\-–]|[\\-–][^ \\-])+)"
		+"(([\n\\.!\\?])|$)"
		,(conf_nbspForParenthesis)?("$1$2 –※$3※–$4 $5$6"):("$1$2 – $3 –$4 $5$6")
		,"einschließend",5));
				
		
		
		//einfache Gedankenstriche (bei allen „ - “, die durch die einschließenden Gedankenstriche nicht abgedeckt wurden)
		//Erkennungsschema: <kein LZ und kein | /*Tabelle, Vorlage*/ >=(1) <ein oder mehr LZ> <Bindestrich, doppelter Bindestrich (--) oder Geviertstrich (—)> <ein oder mehr LZ> <kein LZ>=(3)
		//Ersetzungsschema: (1) <LZ> <Gedankenstrich> <LZ> (3)
		repGedankenstriche.add(new RegExReplacement(-1,"([^ |])[ ]+(-|--|—)[ ]+([^ ])","$1 – $3","einfach",4));

		
		replacements.add(repGedankenstriche);


		
		//Einheiten 
		//*++ da aus Präfixen herausgenommen (wegen „das“, was in den seltensten Fällen „Dekasekunden“ heißen soll …) ++*

		//Leerzeichen bei Einheiten-Quotienten entfernen
		//Erkennungsschema: <Einheit>=(1) <beliebig viele LZ oder nichts> </> <beliebig viele LZ oder nichts> <Einheit>=(2)
		//Ersetzungsschema: (1) </> (2)
		//zwei zusammengesetzte Einheiten 
		replacements.add(new RegExReplacement(-1,"([fpnµmdchkMGT]|)([mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)([²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|) {0,}/ {0,}([fpnµmdchkMGT]|)([mgshlNJ⇦CAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)([²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|)","$1$2$3/$4$5$6","Einheitenquotient",3));
		//eine zusammengesetzte und eine feststehende Einheit
		replacements.add(new RegExReplacement(-1,"([fpnµmdchkMGT]|)([mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)([²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|) {0,}/ {0,}(°C|PS)","$1$2$3/$4","Einheitenquotient",3));
		replacements.add(new RegExReplacement(-1,"(°C|PS) {0,}/ {0,}([fpnµmdchkMGT]|)([mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)([²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|)","$1/$2$3$4","Einheitenquotient",3));
		//zwei feststehende Einheiten
		replacements.add(new RegExReplacement(-1,"(°C|PS) {0,}/ {0,}(°C|PS)","$1/$2","Einheitenquotient",3));  

		if( ! conf_stringBeforePercent.equals("") ){
			
			//String (etwa geschütztes oder normales Leerzeichen)
			//zwischen Zahl und Prozent/Promillzeichen einfügen
			//Erkennungsschema: <Ziffer>=(1) <beliebig viele LZ oder nichts> <Prozent/Promille>=(2)
			//Ersetzungsschema: (1) <der String> (2)
			replacements.add(new RegExReplacement(-1,"([0-9])[ ]*([%‰‱])","$1"+conf_stringBeforePercent+"$2","Prozent",3));

		}
		
		if( conf_nbspForUnit ){
			
			//Geschütztes Leerzeichen zwischen Zahl und Einheit
			//Erkennungsschema: <Zeichen, das kein Buchstabe und keine Ziffer ist (wegen z. B. „C64C/II“) – kann auch ein Komma sein – ODER Zeilenanfang>=(1) <Ziffer(n)>=(2) <beliebig viele LZ oder nichts> (<bekannte Einheit (siehe Benutzer:ComillaBot/Einheiten)> <eventuell / und weitere bekannte Einheit>=(3) <Zeichen, das kein Bindestrich, kein Buchstabe und keine Zahl ist ODER Zeilenende>=(4)
			//Ersetzungsschema: (1) (2) <geschütztes Leerzeichen> (3) (4)
			//*++ zusammenlegen? ++*
			//eine zusammengesetzte Einheit
			replacements.add(new RegExReplacement(-1,"(^|[^a-zA-ZäöüÄÖÜß0-9])([0-9]+) {0,}((?:[fpnµmdchkMGT]|)(?:[mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)(?:[²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|))([^a-zA-ZäöüÄÖÜß0-9\\-]|$)","$1$2※$3$4","gesch. LZ vor Einheit",3));
			//eine feststehende Einheit
			replacements.add(new RegExReplacement(-1,"(^|[^a-zA-ZäöüÄÖÜß0-9])([0-9]+) {0,}(°C|PS)([^a-zA-ZäöüÄÖÜß0-9\\-]|$)","$1$2※$3$4","gesch. LZ vor Einheit",3));
			//Quotient aus zwei zusammengesetzten Einheiten
			replacements.add(new RegExReplacement(-1,"(^|[^a-zA-ZäöüÄÖÜß0-9])([0-9]+) {0,}((?:[fpnµmdchkMGT]|)(?:[mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)(?:[²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|)/(?:[fpnµmdchkMGT]|)(?:[mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)(?:[²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|))([^a-zA-ZäöüÄÖÜß0-9\\-]|$)","$1$2※$3$4","gesch. LZ vor Einheit",3));
			//Quotient aus einer zusammengesetzten und einer feststehenden Einheit
			replacements.add(new RegExReplacement(-1,"(^|[^a-zA-ZäöüÄÖÜß0-9])([0-9]+) {0,}((?:[fpnµmdchkMGT]|)(?:[mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)(?:[²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|)/(?:°C|PS))([^a-zA-ZäöüÄÖÜß0-9\\-]|$)","$1$2※$3$4","gesch. LZ vor Einheit",3));
			replacements.add(new RegExReplacement(-1,"(^|[^a-zA-ZäöüÄÖÜß0-9])([0-9]+) {0,}((?:°C|PS)/(?:[fpnµmdchkMGT]|)(?:[mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)(?:[²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|))([^a-zA-ZäöüÄÖÜß0-9\\-]|$)","$1$2※$3$4","gesch. LZ vor Einheit",3));
			//Quotient aus zwei feststehenden Einheiten
			replacements.add(new RegExReplacement(-1,"(^|[^a-zA-ZäöüÄÖÜß0-9])([0-9]+) {0,}((?:°C|PS)/(?:°C|PS))([^a-zA-ZäöüÄÖÜß0-9\\-]|$)","$1$2※$3$4","gesch. LZ vor Einheit",3));                       
	
			//Geschütztes Leerzeichen zwischen Zahl verlinkter (!) Einheit
			/*++ FUNKTIONIERT NICHT MEHR SEIT SCHUTZÄNDERUNG! ++*/
			//Erkennungsschema: <Zeichen, das kein Buchstabe und keine Ziffer ist (wegen z. B. „C64C/II“) ODER Zeilenanfang>=(1) <Ziffer(n)>=(2) <beliebig viele LZ oder nichts> (<WIKI⇨> <bel. Zeichen, aber kein ⇦> <⇦WIKI_END⇨> <bekannte Einheit (siehe Benutzer:ComillaBot/Einheiten)> <⇦WIKI_CLOSE>)=(3)
			//Ersetzungsschema: (1) (2) <geschütztes Leerzeichen> (3) ⇨⇦
			//eine zusammengesetzte Einheit
			//*++ replacements.add(new RegExReplacement(-1,"(^|[^a-zA-ZäöüÄÖÜß0-9])([0-9]+) {0,}(WIKI⇨[^⇦]*⇦WIKI_END⇨(?:[fpnµmdchkMGT]|)(?:[mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)(?:[²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|)⇦WIKI_CLOSE)","$1$2※$3","gesch. LZ vor Einheit",3));
			//eine feststehende Einheit
			//*++ replacements.add(new RegExReplacement(-1,"(^|[^a-zA-ZäöüÄÖÜß0-9])([0-9]+) {0,}(WIKI⇨[^⇦]*⇦WIKI_END⇨(?:[%‰‱]|°C|PS)⇦WIKI_CLOSE)","$1$2※$3","gesch. LZ vor Einheit",3));
			
		}
		
		//Malzeichen (×)
		
		//zwischen Zahlen
		//Erkennungsschema: (<Ziffer> <(gesch.) LZ>)=(1) <x oder *> (<(gesch.) LZ> <Ziffer>)=(2)
		//Ersetzungsschema: (1)×(2)
		replacements.add(new RegExReplacement(-1,"([0-9][ ※])[x\\*]([ ※][0-9])","$1×$2","Malzeichen",4));
				
		//zwischen Zahlen mit Einheit
		//Erkennungsschema: (<Ziffer> <Einheit> <(gesch.) LZ oder nichts>)=(1) <x oder *> (<(gesch.) LZ oder nichts> <Ziffer(n), evtl mit Komma/Punkt dazwischen> <Einheit>)=(2)
		replacements.add(new RegExReplacement(-1,"([0-9][ ※](?:[fpnµmdchkMGT]|)(?:[mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)(?:[²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|)[ ※]*)[x\\*]([ ※]*(?:[0-9]+[\\.,])?[0-9]+[ ※](?:[fpnµmdchkMGT]|)(?:[mgshlNJCAΩVKTFWJSHB]|Hz|bit|Wb|eV|Pa|Sv|Gy|lx|lm|cd)(?:[²³⁴⁵⁶⁷⁸⁹]|<sup>[0-9]+</sup>|))","$1×$2","Malzeichen",4));
						
		
		if( conf_nbspForPage ){
	
			//Seitenzahlen
			//Erkennungsschema: <LZ oder öffnende runde Klammer>=(1) <„S“> <LZ oder Punkt> <noch mal beliebig viele LZ oder nichts> (<Ziffer(n)> <Punkt, LZ, schließende runde Klammer, Bis-Strich oder f ODER Zeilenende>)=(2)
			//Ersetzungsschema: (1) <„S.“> <geschütztes Leerzeichen> (2)
			replacements.add(new RegExReplacement(-1,"([ \\(])S[\\. ][ ]*([0-9]+[ ]*[f]*(?:[ \\.,\\)\\–]|$))","$1ABK{seite}KBA※$2","Seitenzahl",4)); 
						
		}

		//Apostrophe

		//Apostroph im Wortinneren
		//Erkennungsschema: <Buchstabe>=(1) < ' oder ´ oder ` > <Buchstabe>=(2)
		//Ersetzungsschema: (1) <’> (2)
		replacements.add(new RegExReplacement(-1,"([a-zA-ZäöüÄÖÜß])['´`]([a-zA-ZäöüÄÖÜß])","$1’$2","Apostroph",4)); 
	
		if( false ){
		
			//Apostroph am Wortanfang/-ende (bei ` oder ´ immer)
			//Erkennungsschema: <LZ oder Textanfang>=(1) < ´ oder ` > <Buchstabe>=(2)
			//Ersetzungsschema: (1) <’> (2)
			replacements.add(new RegExReplacement(-1,"(^| )[`´]([a-zA-ZäöüÄÖÜß])","$1’$2","Apostroph",4)); 
			//Erkennungsschema: <Buchstabe>=(1) < ´ oder ` > <LZ oder Satzzeichen>=(2)
			//Ersetzungsschema: (1) <’> (2)
			replacements.add(new RegExReplacement(-1,"([a-zA-ZäöüÄÖÜß])[`´]([ \\.,;:!?])","$1’$2","Apostroph",4)); 
			
			//Apostroph am Wortanfang/-ende (bei ' nur, wenn das Zeichen sonst nicht vorkommt)
			//Erkennungsschema: <Textanfang> (<beliebige Zeichen außer '> <LZ>)=(1) <'> (<Buchstabe> <beliebige Zeichen außer '>) <Textende>=(2)
			//Ersetzungsschema: (1) <’> (2)
			replacements.add(new RegExReplacement(-1,"^([^']* )'([a-zA-ZäöüÄÖÜß][^']*)$","$1’$2","Apostroph",4)); 
			//Erkennungsschema: <Textanfang> (<beliebige Zeichen außer '> <Buchstabe>)=(1) <'> (<LZ oder Satzzeichen> <beliebige Zeichen außer '>) <Textende>=(2)
			//Ersetzungsschema: (1) <’> (2)
			replacements.add(new RegExReplacement(-1,"^([^']*[a-zA-ZäöüÄÖÜß])'([ \\.,;:!?][^']*)$","$1’$2","Apostroph",4)); 
			
		}	
		
		else{
			
			//Apostroph am Wortanfang/-ende
			//Erkennungsschema: <LZ oder Textanfang>=(1) < ´ oder ` > <Buchstabe>=(2)
			//Ersetzungsschema: (1) <’> (2)
			replacements.add(new RegExReplacement(-1,"(^| )['´`]([a-zA-ZäöüÄÖÜß])","$1’$2","Apostroph",4)); 
			//Erkennungsschema: <Buchstabe>=(1) < ´ oder ` > <LZ oder Satzzeichen>=(2)
			//Ersetzungsschema: (1) <’> (2)
			replacements.add(new RegExReplacement(-1,"([a-zA-ZäöüÄÖÜß])['´`]([ \\.,;:!?])","$1’$2","Apostroph",4)); 
					
		}
		
		
		//Minus
		//Erkennungsschema: <= oder LZ>=(1) <Bindestrich oder Gedankenstrich> <Ziffer>=(2)
		//Ersetzungsschema: (1) <Minus> (2)
		replacements.add(new RegExReplacement(-1,"([= ])[-–—]([0-9])","$1−$2","Minus",4)); 
		//Erkennungsschema: <( oder <sup> bzw. <sub>, evtl. LZ>=(1) <Bindestrich oder Gedankenstrich> (<LZ oder nichts> <Ziffer(n)> <evtl. Komma und weitere Ziffern>)=(2) <evtl. LZ, ), | oder </sup> bzw. </sub>>=(3)
		//Ersetzungsschema: (1) <Minus> (2)
		replacements.add(new RegExReplacement(-1,"((?:\\(|<su[bp]>) *)[-–—]( ?[0-9]+(?:[,][0-9]+)?)( *(?:\\)|</su[bp]>))","$1−$2$3","Minus",4)); 
		//Minus in Tabellen oder Vorlagen (nach einem |)
		replacements.add(new RegExReplacement(-1,"(\\| *)[-–—]( ?[0-9]+(?:[,][0-9]+)?)","$1−$2","Minus",4)); 
		
		
 
		//Überflüssige oder fehlende Leerzeichen
		
		ReplacementGroup repLZBeiSatzzeichen = new ReplacementGroup(1,"Leerzeichen");
		
		if( conf_addSpaceAfterPunctuationChars ){
		
			//Kein Leerzeichen nach Satzzeichen (mit Buchstabe vorher oder/und nachher, aber nicht zwischen Zahlen, etwa 3,14)
			//jeweils mit gesonderter Version für Punkte, bei denen keine Änderungen vorgenommen werden, wenn Kleinbuchstaben folgen (dann offenbar kein Satzzeichen)
			//Erkennungsschema: <Buchstabe, Zahl, runde oder eckige schließende Klammer>=(1) <LZ oder nichts> <Punkt, Komma, Semikolon, Doppelpunkt, Ausrufezeichen oder Fragezeichen>)=(2) <Buchstabe>=(3)
			//Ersetzungsschema: (1) (2) <LZ> (3)
			repLZBeiSatzzeichen.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß0-9\\)\\]])[ ]?([,;:!\\?])([a-zA-ZäöüÄÖÜß])","$1$2 $3","nach Satzzeichen",2));
			repLZBeiSatzzeichen.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß0-9\\)\\]])[ ]?([\\.])([A-ZÄÖÜ])","$1$2 $3","nach Satzzeichen", 2));
			//Erkennungsschema: <Buchstabe, runde oder eckige schließende Klammer>=(1) <LZ oder nichts> <Punkt, Komma, Semikolon, Doppelpunkt, Ausrufezeichen oder Fragezeichen>)=(2) <Buchstabe oder Zahl>=(3)
			//Ersetzungsschema: (1) (2) <LZ> (3)
			repLZBeiSatzzeichen.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß\\)\\]])[ ]?([,;:!\\?])([a-zA-ZäöüÄÖÜß0-9])","$1$2 $3","nach Satzzeichen",2));
			repLZBeiSatzzeichen.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß\\)\\]])[ ]?([\\.])([A-ZÄÖÜ0-9])","$1$2 $3","nach Satzzeichen",2));
			
		}
							 
		//Leerzeichen vor Satzzeichen
		//Erkennungsschema: <Buchstabe, Zahl, runde oder eckige schließende Klammer>=(1) <LZ> <Punkt, Komma, Semikolon, Doppelpunkt, Ausrufezeichen oder Fragezeichen>)=(2) <LZ oder Zeilenende>=(3)
		//Ersetzungsschema: (1) (2) <LZ> (3)
		repLZBeiSatzzeichen.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß0-9)\\]]) ([\\.,;:!\\?])([ \n])","$1$2 $3","vor Satzzeichen",2));
		//Erkennungsschema: <Buchstabe, runde oder eckige schließende Klammer>=(1) <LZ> <Punkt, Komma, Semikolon, Doppelpunkt, Ausrufezeichen oder Fragezeichen>)=(2)
		//Ersetzungsschema: (1) (2)
		repLZBeiSatzzeichen.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß)\\]]) ([\\.,;:!\\?])","$1$2","vor Satzzeichen",2));
		
		replacements.add(repLZBeiSatzzeichen);
		 
		//überflüssiges Leerzeichen vor Satzzeichen am Ende eines Zitats
		//muss gesondert behandelt werden, da danach kein LZ stehen darf
		//muss nach Anführungszeichen-Ersetzung stattfinden, da vorher möglicherweise noch " statt “ steht
		//Erkennungsschema: <Buchstabe, Zahl, runde oder eckige schließende Klammer>=(1) <LZ> <Punkt, Ausrufezeichen oder Fragezeichen>)=(2) <LZ oder nichts> <schließendes Anführungszeichen>
		//Ersetzungsschema: (1) (2) <schließendes Anführungszeichen>
		replacements.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß0-9)\\]]) ([\\.!\\?]) ?“","$1$2“","LZ bei Satzzeichen in Zitat",2));
			
		//Leerzeichensetzung bei (runden) Klammern
		//Anmerkung: Nur Klammern mit mindestens einem Leerzeichen werden bearbeitet, um Korrekturen von Klammern mit direkten Wortanschluss zu vermeiden: Bauarbeiter(innen), Tele(trans)portation. Leider werden dadurch auch einige Fehler nicht erkannt. /*++ verbesserbar? ++*
		//Fall 1: Buchstabe oder Zahl folgt
		//Erkennungsschema: <Buchstabe oder Zahl>=(1) <beliebig viele LZ oder nichts> <runde öffnende Klammer> <beliebig viele LZ oder nichts> <beliebige Zeichen (mind. 2), dazwischen mindestens einmal ein LZ, aber keine schließende runde Klammer, kein … ; am Anfang und Ende keine Leerzeichen (da diese sonst nicht entfernt würden) und kein Bindestrich; kein Fragezeichen, außer als letztes (aber nicht einziges) Zeichen>=(2) <beliebig viele LZ oder nichts> <runde schließende Klammer> <Buchstabe oder Zahl>=(3)
		//Ersetzungsschema: (1) <LZ> <runde öffnende Klammer> (2) <runde schließende Klammer> <LZ> (3)
		replacements.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß0-9]) {0,}[\\(] {0,}([^\\)…\\? ]{1,} [^\\)…\\? ]{0,}[^\\)…\\? \\-]|[^\\)…\\? ]{1,} [^\\)…\\? ]{0,}\\?|[^\\) ][^\\)]{0,} [^\\)]{0,}[^\\) \\-]) {0,}[\\)] {0,}([A-Za-zäöüÄÖÜß0-9])","$1 ($2) $3","LZ bei Klammern",2)); 
		//Fall 2: Satzzeichen folgt
		//Anmerkung: in der Klammer muss mindestens ein Leer- oder Satzzeichen sein, um Korrekturen etwa in Bauarbeiter(innen) oder anderen Fällen von Klammern mit direkten Wortanschluss zu vermeiden
		//Erkennungsschema: <Buchstabe oder Zahl>=(1) <beliebig viele LZ oder nichts> <runde öffnende Klammer> <beliebig viele LZ oder nichts> <beliebige Zeichen (mind. 2), dazwischen mindestens einmal ein LZ, aber keine schließende runde Klammer, kein … ; am Ende keine Leerzeichen (siehe oben) und kein Bindestrich; kein Fragezeichen, außer als letztes (aber nicht einziges) Zeichen>=(2) <beliebig viele LZ oder nichts> <runde schließende Klammer> <beliebig viele LZ oder nichts> <Satzzeichen>=(3)
		//Ersetzungsschema: (1) <LZ> <runde öffnende Klammer> (2) <runde schließende Klammer> (3)
		replacements.add(new RegExReplacement(-1,"([A-Za-zäöüÄÖÜß0-9]) {0,}[\\(] {0,}([^\\)…\\? ]{1,} [^\\)…\\? ]{0,}[^\\)…\\? \\-]|[^\\)…\\? ]{1,} [^\\)…\\? ]{0,}\\?|[^\\) ][^\\)]{0,} [^\\)]{0,}[^\\) \\-]) {0,}[\\)] {0,}([\\.,;:!\\?])","$1 ($2)$3","LZ bei Klammern",2)); 
		 		
		
		if (conf_expandMarkup) {

			ReplacementGroup repSatzzeichenAuszeichnen = new ReplacementGroup(1,"Textauszeichnung für angrenzende Zeichen");
						
			//Auszeichnung des gesamten Klammerninhalts für angrenzende
			//Satzzeichen übernehmen.
			//Erkannte Auszeichnungen: Fett ('''), Kursiv ('')
			
			//Beschränkungen derzeit: Es darf kein ' im formatierten Text sein
			
			//runde Klammern
			repSatzzeichenAuszeichnen.add(new RegExReplacement(-1,"\\('''([^\\)']+)'''\\)","'''($1)'''","fett",2));
			repSatzzeichenAuszeichnen.add(new RegExReplacement(-1,"\\(''([^\\)']+)''\\)","''($1)''","kursiv",2));
	
			//Anführungszeichen
			repSatzzeichenAuszeichnen.add(new RegExReplacement(-1,"„'''([^“']+)'''“","'''„$1“'''","fett",2));
			repSatzzeichenAuszeichnen.add(new RegExReplacement(-1,"„''([^“']+)''“","''„$1“''","kursiv",2));
		
			replacements.add(repSatzzeichenAuszeichnen);			
			
		}
		
		//Überflüssige Leerzeichen entfernen
		if( conf_Whitespace ){
		
			//Mehrfache Leerzeichen
			//Erkennungsschema: <zwei oder mehr LZ>
			//Ersetzungsschema: <LZ>
			replacements.add(new RegExReplacement(-1," {2,}"," ","mehrfache LZ",2));
	 
			//Leerzeichen am Zeilenende
			//Erkennungsschema: <LZ> <Zeilenende>
			//Ersetzungsschema: <Zeilenende>
			replacements.add(new RegExReplacement(-1," \n","\n","LZ am Zeilenende"));
			replacements.add(new RegExReplacement(-1," $","","LZ am Zeilenende"));		
			
		}
				
		
		// AUFHEBEN TEMPORÄRER ERSETZUNGEN


		//Schutz von Abkürzungen aufheben
	
		//B.C.
		replacements.add(new RegExReplacement(-1,"ABK\\{beforechrist\\}KBA","B.C.")); 
		//c. t.
		replacements.add(new RegExReplacement(-1,"ABK\\{cumtempore\\}KBA","c. t.")); 
		replacements.add(new RegExReplacement(-1,"ABK\\{cumtempore_nbsp\\}KBA","c.※t.")); 
		//d. h.
		replacements.add(new RegExReplacement(-1,"ABK\\{dasheißt\\}KBA","d. h.")); 
		replacements.add(new RegExReplacement(-1,"ABK\\{dasheißt_nbsp\\}KBA","d.※h.")); 
		//etc.
		replacements.add(new RegExReplacement(-1,"ABK\\{et_cetera\\}KBA","etc.")); 
		//e. V.
		replacements.add(new RegExReplacement(-1,"ABK\\{eingetragenerverein\\}KBA","e. V."));
		replacements.add(new RegExReplacement(-1,"ABK\\{eingetragenerverein_nbsp\\}KBA","e.※V."));
		//F.A.Z.
		replacements.add(new RegExReplacement(-1,"ABK\\{FrankfurterAllgemeineZeitung\\}KBA","F.A.Z."));
		//i. d. R.
		replacements.add(new RegExReplacement(-1,"ABK\\{inderregel\\}KBA","i. d. R."));
		replacements.add(new RegExReplacement(-1,"ABK\\{inderregel_nbsp\\}KBA","i.※d.※R."));
		//.NET
		replacements.add(new RegExReplacement(-1,"ABK\\{dotnet\\}KBA",".NET"));
		//o. Ä.
		replacements.add(new RegExReplacement(-1,"ABK\\{oderähnliches\\}KBA","o. Ä."));
		replacements.add(new RegExReplacement(-1,"ABK\\{oderähnliches_nbsp\\}KBA","o.※Ä."));
		//s. o.
		replacements.add(new RegExReplacement(-1,"ABK\\{sieheoben\\}KBA","s. o.")); 
		replacements.add(new RegExReplacement(-1,"ABK\\{sieheoben_nbsp\\}KBA","s.※o.")); 
		//s. t.
		replacements.add(new RegExReplacement(-1,"ABK\\{sinetempore\\}KBA","s. t.")); 
		replacements.add(new RegExReplacement(-1,"ABK\\{sinetempore_nbsp\\}KBA","s.※t.")); 
		//s. u.
		replacements.add(new RegExReplacement(-1,"ABK\\{sieheunten\\}KBA","s. u.")); 
		replacements.add(new RegExReplacement(-1,"ABK\\{sieheunten_nbsp\\}KBA","s.※u.")); 
		//u. a.
		replacements.add(new RegExReplacement(-1,"ABK\\{undandere\\}KBA","u. a."));
		replacements.add(new RegExReplacement(-1,"ABK\\{undandere_nbsp\\}KBA","u.※a."));
		//u. Ä.
		replacements.add(new RegExReplacement(-1,"ABK\\{undähnliches\\}KBA","u. Ä."));
		replacements.add(new RegExReplacement(-1,"ABK\\{undähnliches_nbsp\\}KBA","u.※Ä."));
		//u. v. a.
		replacements.add(new RegExReplacement(-1,"ABK\\{undvieleandere\\}KBA","u. v. a."));
		replacements.add(new RegExReplacement(-1,"ABK\\{undvieleandere_nbsp\\}KBA","u.※v.※a."));
		//v. a.
		replacements.add(new RegExReplacement(-1,"ABK\\{vorallem\\}KBA","v. a."));
		replacements.add(new RegExReplacement(-1,"ABK\\{vorallem_nbsp\\}KBA","v.※a."));
		//U.S.
		replacements.add(new RegExReplacement(1,"ABK\\{unitedstates\\}KBA","U.S."));
		//z. B.
		replacements.add(new RegExReplacement(-1,"ABK\\{zumbeispiel\\}KBA","z. B.")); 
		replacements.add(new RegExReplacement(-1,"ABK\\{zumbeispiel_nbsp\\}KBA","z.※B."));
		//z. T.
		replacements.add(new RegExReplacement(-1,"ABK\\{zumteil\\}KBA","z. T.")); 
		replacements.add(new RegExReplacement(-1,"ABK\\{zumteil_nbsp\\}KBA","z.※T.")); 
		//S.
		replacements.add(new RegExReplacement(-1,"ABK\\{seite\\}KBA","S.")); 
				 
	
		//Maskierung der HTML-Sonderzeichen rückgängig machen
		replacements.add(new RegExReplacement(-1,"HTML\\{([^ ;\\}]+)\\}LMTH","&$1;"));
		
		//Maskierung der geschützten Leerzeichen rückgängig machen
		replacements.add(new RegExReplacement(-1,"※","&nbsp;"));
		
		
		//Aufruf der Schutzfunktionalität (Aufheben des Schutzes) durch ein Replacement mit eigener Methode
		replacements.add( new CustomReplacement(){				
			public String apply(String s){
				return unprotect(s);
			}
		}	);	
		
				
	}
		
	
	//Schutzfunktionalität, die dazu dient, bestimmte Bereiche von der Bearbeitung auszunehmen.
			
	/**
	 * ersetzt gefährdete Bereiche durch Markierungen
	 */	 
	private String protect(String s){
			
		
		/*++ erlauben, Teile der Bereiche nicht zu schützen (z.B. in Weblinks den Namen nach der Adresse) ++*/
								
		
		////////////////////////////////////////////////////////////////////
		// wahrscheinlich interessantester Teil der Schutzfunktionalität, //
		// hier stehen alle zu schützenden Bereiche.                      //
		////////////////////////////////////////////////////////////////////		
		
		String[] protections = {
	
			//Kommentare  
			//Erkennungsschema: <„<!--“> <so wenige Zeichen wie möglich (Reluctant quantifier)>=(1) <„-->“>
			"<!--.*?-->",
		
			//Textbereiche in Tags (<math>, <gallery>, <timeline>, <code>, <pre>)
			//Die möglichen Tags sind exakt vorgegeben, da in manchen Tags Änderungen wünschenswert sind. 
			//Erkennungsschema: <„<“> <bel. LZ> <„math“, „gallery“ …>=(1) <bel. LZ> <„>“> <so wenige Zeichen wie möglich (Reluctant quantifier)> <„</“> <bel. LZ> (1) <bel. LZ> <„>“>
			"< *[mM][aA][tT][hH] *>.*?</ *[mM][aA][tT][hH] *>",
			"< *[gG][aA][lL][lL][eE][rR][yY] *>.*?</ *[gG][aA][lL][lL][eE][rR][yY] *>",  //TODO: sobald möglich hier die Texte NICHT schützen
			"< *[tT][iI][mM][eE][lL][iI][nN][eE] *>.*?</ *[tT][iI][mM][eE][lL][iI][nN][eE] *>", //TODO: sobald möglich hier die Texte NICHT schützen
			"< *[cC][oO][dD][eE] *>.*?</ *[cC][oO][dD][eE] *>",
			"< *[pP][rR][eE] *>.*?</ *[pP][rR][eE] *>",
			
			//Vorformatierte Bereiche (Zeilen, die mit einem LZ beginnen)
			"^ .*$",
			
			//Text in Ersatz-Anführungszeichen (auch einfachen) nach = (z.B. width="300")
			//Erkennungsschema: <=> <beliebig viele LZ> <"> <so wenige Zeichen wie möglich (Reluctant quantifier)> <">
			"= *\".*?\"",
			//entsprechend mit einfachen Anführungszeichen
			"= *'.*?'",
			
			//Prozentangaben für width und height schützen
			//Erkennungsschema: <width oder height> <=> <Ziffern> <%>
			"width=[0-9]+%",				
			"height=[0-9]+%",
			
			//Wikilinks
			//Wikilink ohne „|“
			"\\[\\[[^\\]\\|]*\\]\\]",
			//Wikilink mit „|“, d.h. mit korrigierbarem letzten Abschnitt (bei mehreren |, etwa bei Bildern, wird das letzte als WIKI_END gesetzt (das, nach dem kein | mehr kommt)
			/*++ replacements.add(new RegExReplacement(-1,"\\[\\[([^\\[\\]]*)\\|([^\\]\\|]*)\\]\\]","WIKI⇨$1⇦WIKI_END⇢$2⇠WIKI_CLOSE")); ++*/
			"\\[\\[([^\\[\\]\\|]*)\\|", /*++ <-- temporär ++*/
			
			//weblinks
			//http-Link ohne Leerzeichen
			/*++ replacements.add(new RegExReplacement(-1,"\\[http:([^\\] ]*)\\]","HTTP⇨$1⇦HTTP_END")); ++*/
			//http-Link mit Leerzeichen (muss nach der Variante ohne LZ kommen, da der http-Link nach Anwendung dieser Regel hier ja ohne LZ ist)
			/*++ replacements.add(new RegExReplacement(-1,"\\[http:([^\\] ]*) ([^\\]]*)\\]","HTTP⇨$1⇦HTTP_END⇢$2⇠HTTP_CLOSE")); ++*/
			/*++ vorerst nur: generell, keine Rücksicht auf mit oder ohne LZ; am Anfang muss etwas stehen, was ein Protokoll sein könnte ++*/
			"\\[[a-zA-Z]+:[^\\] ]*[\\] ]",
			
			//Templates (Vorlagen)  
			"\\{\\{[^\\}]*\\}\\}",
				
			//Einrückungen
			//einfache Einrückung (Doppelpunkte am Zeilenanfang)
			"^[:]++",
			//Einrückung in Aufzählung (* am Zeilenanfang, dann Doppelpunkte)
			"^\\*[:]++",
			//Einrückung mit Überschrift (Semikolon am Zeilenanfang, Überschrift, Doppelpunkt)
			"^;[^:]*:",
			
			//Webadresse mit www
			"(?:http[^\n ⇦]*)?www\\.[^\n: ⇦]*\\.[^\n \\.⇦]*(?:/[^\n: ⇦]*)?",
			/*++ so: "(?:http[^\n ⇦]*)?www\\.[^\n: ⇦]*\\.[^\n \\.⇦]*(?:/[^\n: ⇦]*)?", ++*/
			//Webadresse ohne www, aber am Ende mit einer der üblichen Endungen: .de .ch .en .org .com .info
			"(?:http[:]?)?[^\n: ⇦]*\\.(?:de|at|ch|en|us|org|com|info)(?:/[^\n: ⇦]*)?", 
			//Mailadresse
			"(?:mailto[^\n ⇦]*)?[^\n ⇦]*@[^\n ⇦]*\\.[^\n \\.⇦]*",
			//Newsgroup
			"news:[^\n ⇦]+\\.[^\n \\.⇦]+",
			
			//ISBN, ISSN
			"(?:ISBN(?:-10|-13)?|ISSN)(?:&nbsp;| )?[0-9X\\-]{7,}",
			
			//Tabellenüberschrift-Zeilen (beginnen mit !, danach folgen mehrfach !!)
			/* TODO: die Überschriften selbst NICHT schützen */
			"(^!.*!!.*$)"
			
		};
		
		////////////////////////////////////////////////////////////////////	
		
		
		//Alle Vorkommen obiger Bereiche durch eindeutige Marker ersetzen
			
		return stringProtector.protect(s, protections);
		
	}

	/**
	 * setzt für die Markierungen wieder die Textstücke ein
	 */	
	private String unprotect(String s){
		
		return stringProtector.unprotect(s);	
				
	}

	
	
	//Aufspaltungs-Funktionalität
	
	private class ComillaSplitter extends Splitter {
		
		//Zeichenketten zur Markierung eines Zeilenumbruchs / geschützten Zeilenumbruchs.
		/*++ TODO: abhängig vom Text wählen (irgendwas, das nicht vorkommt) ++*/
		private String NL = "⇦※⇨";
		private String PNL = "≑";
		
		/**
		 * trennt den Text an Zeilenumbrüchen, aber nicht innerhalb von Schutzbereichen.
		 */
		public String[] split(String s){				
			
				//Zeilenumbrüche in Kommentaren schützen
			
				s = replace("<!--((?:.|\n)*?)-->","COMMENT⇨$1⇦TNEMMOC",s);
							
				s = replace("COMMENT⇨([^⇦]*?)"+"\n"+"([^⇦]*?)⇦TNEMMOC","COMMENT⇨$1"+PNL+"$2⇦TNEMMOC",s);
		
				s = replace("COMMENT⇨(.*?)⇦TNEMMOC","<!--$1-->",s);
						
				//Zeilenumbrüche in Tags schützen
			
				s = replace("<( *([^/>]+) *)>(.*?)</( *(\\2) *)>","TAG$1TAG⇨$3⇦GAT$4GAT",s);	
							
				s = replace("TAG([^>⇨⇦]+)TAG⇨([^⇦]*?)"+"\n"+"([^⇦]*?)⇦GAT\\1GAT","TAG$1TAG⇨$2"+PNL+"$3⇦GAT$1GAT",s);
			
				s = replace("TAG( *([^⇨⇦/]+) *)TAG⇨(.*?)⇦GAT( *(\\2) *)GAT","<$1>$3</$4>",s);
				
				//Zeilenumbrüche in Templates schützen
			
				s = replace("\\{\\{([^\\}]*?)\\}\\}","TEMPLATE⇨$1⇦ETALPMET",s);
			
				s = replace("TEMPLATE⇨([^⇦]*)"+"\n"+"([^⇦]*)⇦ETALPMET","TEMPLATE⇨$1"+PNL+"$2⇦ETALPMET",s);
			
				s = replace("TEMPLATE⇨(.*?)⇦ETALPMET","{{$1}}",s);
											
				//Zeilenumbrüche nach Überschriften schützen 
				
				s = replace("=((?:"+PNL+")*)\n","=$1"+PNL,s);
								
				//Zeilenumbrüche zwischen Aufzählungspunkten schützen
					
				s = replace("(\\*[^\\*]*)\n( *\\*)","$1"+PNL+"$2",s);

				if( conf_splitEnumerationAfter > 0 ){		
					
					//nach jedem n-ten Aufzählungspunkt Schutz entfernen
					
					String from = "";
					String to = "";
					
					for( int i=1; i<conf_splitEnumerationAfter; ++i){						
						from += "(\\*[^\\*\n]*)"+PNL+"( *)";
						to += "$"+(i*2-1)+PNL+"$"+(i*2);						
					}

					from += "(\\*[^\\*\n]*)"+PNL+"( *\\*)";
					to += "$"+(conf_splitEnumerationAfter*2-1)+"\n"+"$"+(conf_splitEnumerationAfter*2);
									
					s = replace(from,to,s);
				
				}
				
				
				//alle ungeschützten Zeilenumbrüche durch Trennmarkierungen ersetzen 
			
				boolean replacementComplete = false;
					
				while( !replacementComplete ){
				
					String textBeforeReplacement = new String(s);
					
					Pattern p = Pattern.compile("(?:\n|\r\n|\r|\u0085|\u2028|\u2029)",Pattern.MULTILINE);	
					
					Matcher m = p.matcher(s);
				
					s = m.replaceAll(NL);					
				
					if( s.compareTo(textBeforeReplacement) == 0 ){ replacementComplete = true; }
				
				}     
				
				
				//Zeilenumbruch-Schutzersetzungen aufheben
				
				Pattern p = Pattern.compile(PNL);	
						
				Matcher m = p.matcher(s);
			
				s = m.replaceAll("\n");
				
				//split the text
				
				Pattern p3 = Pattern.compile(NL); 
				
				return p3.split(s);		
				
		}
		
		/**
		 * fügt den Text zusammen, Zeilenumbrüche werden zwischen den Arrayelementen platziert.
		 */
		public String unsplit(String[] sArray){
			
			if( sArray == null || sArray.length == 0 ){ return null; }
			
			else{
				
				String result = sArray[0];
				
				for( int i=1; i<sArray.length; ++i ){
					result += "\n" + sArray[i];
				}
				
				return result;
				
			}
			 
		}		
		
		/**
		 * Hilfsmethode: wendet eine Ersetzung per regex so lange auf einen String an, bis keine Änderung mehr erfolgt.
		 */		 
		private String replace(String regex, String rwith, String s){
			 
			boolean replacementComplete = false;
			
			while( !replacementComplete ){
					
				String sBeforeReplacement = new String(s);
		
				Pattern p = Pattern.compile(regex,Pattern.DOTALL|Pattern.MULTILINE);	
						
				Matcher m = p.matcher(s);
				
				s = m.replaceAll(rwith);
			
				if( s.equals(sBeforeReplacement) ){ replacementComplete = true; }
					
			} 
			
			return s;
			
		}
				
	}
	
	
	public MediaWikiText[] edit(EditableText text, UIComponent uiForRequests){
		
		//Prüfen, ob Artikel schweizbezogen ist, evtl. Warnung ausgeben
		boolean schweizbezogen = Pattern.compile("<!-- *schweizbezogen *-->").matcher(text.getText()).find();
		
		if( schweizbezogen ){
			uiForRequests.request(new UIRequest_Output(str("swiss")));
		}
		
		/*
		 * prüfen, ob Artikel Pinyin-Wörter mit Apostrophen enthält
		 * (wortweise mit Pinyin-Wort-Regex vergleichen),
		 * evtl. Warnung ausgeben
		 */
		
		if (conf_pinyinApostropheWarning) {
						
			LinkedList<String> pinyinWords = new LinkedList<String>();
			
			{
				String[] words = text.getText().split("(?:\\s+|''+|[\"„“\\(\\)\\[\\]{}])");
				
				Pattern pinyinPattern = Pattern.compile(PINYIN_REGEX);
				
				for (String word : words) {
					
					String wordLowerCase = word.toLowerCase();
					
					if (word.contains("'")
							&& pinyinPattern.matcher(wordLowerCase).matches()) {
						
						pinyinWords.add(word);
						
					}
					
				}			
			}
			
			if( !pinyinWords.isEmpty() ){	

				StringBuffer wordList = new StringBuffer();
				
				for (String pinyinWord : pinyinWords) {
					wordList.append(pinyinWord).append("\n");
				}
				
				uiForRequests.request(new UIRequest_Output(
						str("pinyin", wordList.toString())));
			}
		
		}
		
		//Ersetzungen generieren		
		createReplacements(schweizbezogen);
		
		//Ersetzungen durchführen
		MediaWikiText[] result = super.edit(text,uiForRequests);
		
		//Beschreibung (default: „Typografische Korrekturen:“, konfigurierbar) vor die Liste der Ersetzungen schreiben		
		if( result != null && result.length > 0 && !result[0].getEditSummary().equals("") ){			
			result[0].setEditSummary(conf_EditDescription + result[0].getEditSummary());			
		}
		
		//Auf kritische Zeichen prüfen. Falls enthalten:
		//Ergebnis auf null setzen (Abbruch des Korrekturlaufs) und Fehlerhinweis
		for( String problemString : problemStrings ){
			if( result != null ){
				for( MediaWikiText r : result ){
					if( r.getText().contains(problemString) ){
						uiForRequests.request(new UIRequest_Output(
								str("problemString", problemString)));					
						result = null;
						break;
					}
				}
			}
		}		
		
		return result;
		
	}
		
	//constructor
	
	public ComillaReplacer(){		
	
		super();
		
		super.splitter = new ComillaSplitter();
				
	}
		
}

